//
//  Controller.m
//  SimpleSearch
//
//  Created by Ryan on Thu Oct 30 2003.
//
//  ****************
//  Short Disclaimer (please read!):
//
//  Why open source?  This project was briefly popular several years ago and has since lost interest (on both
//  my part and the public's part).  Rather than letting it moulder for eternity, I decided to open source it 
//  in case anyone else is interested in renovating it and bringing it up to date.
//
//  My main concern with open sourcing it is that this program is one of the first Cocoa programs I wrote and
//  as such, it is in no way representative of my current coding style!  Many things are done incorrectly in 
//  this code base but I have not taken the time to revise them for the open source release. Hence, if you work
//  for a company looking to hire me, don't look too critically at this old code!
//
//  This code was originally written in 2002 to 2003 and hence was created before technologies such as 
//  Cocoa Bindings and properties (Obj-C 2.0) existed.  Much of the code could be ripped out and replaced 
//  with the newer way of doing things.
//
//  The GUI is also fairly out of date and in need of some serious updating.
//  
//  Have fun!
//  April, 2008.
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.


#import "Controller.h"
#import "PreferenceController.h"
#import "Model.h"
#import "SmartTextField.h"
#import "Plugin.h"


@interface Controller ()

- (void)setupMouseMovedTimer;

- (void)readDefaultRadioButtonChoices;
- (void)writeDefaultRadioButtonChoices;

- (void)makeWindowTall:(BOOL)b;

@end


@implementation Controller


- (void)makeWindowTall:(BOOL)b {
	int newHeight, newWidth;
	
	newWidth = [searchWindow frame].size.width;
	
	if (b) {  // Make it tall
		[disclosureTriangle setState: YES];
		[disclosureTriangle setToolTip: @"Hide Options"];
		[showHideOptionsMainMenu setTitle: @"Hide Options"];
		[showHideOptionsContextualMenu setTitle: @"Hide Options"];
		[showHideOptionsDockMenu setTitle: @"Hide Options"];	
		
		newHeight = TALLHEIGHT;
	} else {  // Make it short
		[disclosureTriangle setState: NO];
		[disclosureTriangle setToolTip: @"Show Options"];
		[showHideOptionsMainMenu setTitle: @"Show Options"];
		[showHideOptionsContextualMenu setTitle: @"Show Options"];
		[showHideOptionsDockMenu setTitle: @"Show Options"];	
		
		newHeight = SHORTHEIGHT;		
	}
	
	
	// Get the frame for the content of the searchWindow
	NSRect aFrame = [NSWindow contentRectForFrameRect:[searchWindow frame] 
											styleMask:[searchWindow styleMask]];
	
	// Move the origin so the window resizes as we want it to.
	aFrame.origin.y += aFrame.size.height;
	aFrame.origin.y -= newHeight;
	
    aFrame.size.height = newHeight;
    aFrame.size.width = newWidth;
	
	// Now, get the frame for the searchWindow by passing it the
	// frame for the content that we just moved the origin for.
	aFrame = [NSWindow frameRectForContentRect:aFrame 
									 styleMask:[searchWindow styleMask]];
	
	// Set the new frame.
	[searchWindow setFrame:aFrame display:YES animate:YES];
	
	[leftView setNeedsDisplay:YES];
	[rightView setNeedsDisplay:YES];
	[[searchWindow contentView] setNeedsDisplay:YES];	
	
}


// Open the readme file using a unix command
- (IBAction)openReadme:(id)sender {
	
	NSBundle * bund = [NSBundle mainBundle];
	NSString * path = [NSString stringWithFormat: 
		@"%@/Contents/Resources/readme.rtfd", [bund bundlePath]];
	
	[[NSWorkspace sharedWorkspace] openFile: path];
}



// Toggles the height of the search window between tall and short.
- (IBAction)toggleWindowHeight:(id)sender {
	
	if ([searchWindow frame].size.height == SHORTHEIGHT) { 
		[self makeWindowTall: YES];
	} else {
		[self makeWindowTall: NO];
	}
	
}


- (IBAction)showPreferencePanel:(id)sender {
	// Is preferenceController nil? 
	if (!preferenceController) {
		preferenceController = [[PreferenceController alloc] init];
	}
	[preferenceController showWindow:self];
}


// The user clicked in one of the drag views on the left or right
// and we are now receiving the notification of this.
- (void)dragViewMouseDown: (NSNotification *)note {

}


// The user released the mouse in one of the drag views on the left or right.
- (void)dragViewMouseUp: (NSNotification *)note {
	DragView * theView = [note object];

	// Only expand/collapse the window if the user wasn't just dragging the mouse.
	if (! [theView didMouseJustDrag]) {

		/*
		if (![theView isOnLeft]) {
			NSLog(@"RightFrame");
		} else {
			NSLog(@"LeftFrame");
		}
		*/
		
		if (isCollapsed) { [self expandWindowOutOfView: theView]; }
		else { [self collapseWindowIntoView: theView]; }
	}
}


// Collapse the main window into the DragView that the user clicked on...
- (void)collapseWindowIntoView: (DragView *)view {
	int newHeight, newWidth;
	
	newWidth = [view frame].size.width;
	newHeight = [searchWindow frame].size.height;
	
	// Get the frame for the content of the searchWindow.
	NSRect aFrame = [NSWindow contentRectForFrameRect:[searchWindow frame] 
										styleMask:[searchWindow styleMask]];
	
	// Set the origin depending on whether they clicked on the right or left.
	NSPoint newOrigin;
	
	newOrigin.y = aFrame.origin.y;
	if ([view isOnLeft]) {
		newOrigin.x = aFrame.origin.x;
	} else {
		newOrigin.x = aFrame.origin.x + aFrame.size.width - [view frame].size.width;
	}

	aFrame.origin = newOrigin;
	
	
	aFrame.size.height = newHeight;
    aFrame.size.width = newWidth;
	
	// Now, get the frame for the searchWindow by passing it the
	// frame for the content that we just resized.
	aFrame = [NSWindow frameRectForContentRect:aFrame 
						styleMask:[searchWindow styleMask]];
	

	// Record the frame of the window before collapsing it	.
	originalWindowFrame = [searchWindow frame];
	
	// Set the new frame.
	[searchWindow setFrame:aFrame display:YES animate:YES];	
	
	collapsedWithLeftView = [view isOnLeft];	
	isCollapsed = YES;

	// This is a bit of a mess, but seems to be necessary.
	// Basically, if they click on the left to collapse the window,
	// then move the right DragView down by 100 pixels so it's out of the way.
	// If they clicked on the right to collapse, move the left 100 pixels down.
	if (collapsedWithLeftView) {
		[rightView moveDown];
//		[rightView setOriginYCoordinate: -500.0];
	} else {
		[leftView moveDown];
//		[leftView setOriginYCoordinate: -500.0];
	}
	
	[[searchWindow contentView] setNeedsDisplay:YES];	
	
	[leftView setToolTip: @"Click to expand window."];
	[rightView setToolTip: @"Click to expand window."];	
}


- (void)expandWindowOutOfView: (DragView *)view {
	int newHeight, newWidth;
	
	newWidth = originalWindowFrame.size.width;
	newHeight = [searchWindow frame].size.height;
	
	// Get the frame for the content of the searchWindow.
	NSRect aFrame = [NSWindow contentRectForFrameRect:[searchWindow frame] 
											styleMask:[searchWindow styleMask]];
	
	// Set the origin depending on whether they clicked on the right or left.
	NSPoint newOrigin;
	
	newOrigin.y = aFrame.origin.y;
	if ([view isOnLeft]) {
		//NSLog(@"expanding, was on left");
		newOrigin.x = aFrame.origin.x;
		[rightView moveUp];		
	} else {
		//NSLog(@"expanding, was on right");	
		newOrigin.x = aFrame.origin.x - originalWindowFrame.size.width + aFrame.size.width;
		[leftView moveUp];
	}
	
	aFrame.origin = newOrigin;
	
	aFrame.size.height = newHeight;
    aFrame.size.width = newWidth;
	
	// Now, get the frame for the searchWindow by passing it the
	// frame for the content that we just resized.
	aFrame = [NSWindow frameRectForContentRect:aFrame 
						styleMask:[searchWindow styleMask]];
	
		
	// Set the new frame.
	[searchWindow setFrame:aFrame display:YES animate:YES];	
	
	
	/*
	NSRect newFrame;

	newFrame.origin = [leftView frame].origin;
	newFrame.origin = [searchWindow convertBaseToScreen: newFrame.origin];
	newFrame.origin.y = [searchWindow frame].origin.y;
	
	if (! collapsedWithLeftView) { 		//if they collapsed it from the right
		newFrame.origin.x -= originalWindowFrame.size.width;
		newFrame.origin.x += [leftView frame].size.width;
	}

	newFrame.size.width = originalWindowFrame.size.width;
	newFrame.size.height = originalWindowFrame.size.height;
*/
	isCollapsed = NO;
	[searchWindow setFrame: aFrame display:YES animate:YES];


	[leftView setNeedsDisplay:YES];
	[rightView setNeedsDisplay:YES];
	[[searchWindow contentView] setNeedsDisplay:YES];
	
	[leftView setToolTip: @"Click to contract window."];
	[rightView setToolTip: @"Click to contract window."];	
}


// Called when the user updates the preferences (currently, only gets
// called for transparency and dock icon).
- (void)preferencesUpdated: (NSNotification *)note  {
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	[searchWindow setFloatingPanel: [defaults boolForKey:@"FloatingWindow"]];
    [searchWindow setAlphaValue: 
		[defaults floatForKey:@"transparency"] / 100.0];
	
}


- (IBAction)selectNextSite:(id)sender {
	
	int i = [siteMenu indexOfSelectedItem];
	int num = [siteMenu numberOfItems];
	
	i++;
	
	if (i >= num) i = 0;
	
	[siteMenu selectItemAtIndex: i];
	
	[self popupMenuChanged:self];
}

- (IBAction)selectPreviousSite:(id)sender {
	int i = [siteMenu indexOfSelectedItem];
	int num = [siteMenu numberOfItems];
	
	i--;
	
	if (i < 0) i = num - 1;
	
	[siteMenu selectItemAtIndex: i];
	[self popupMenuChanged:self];
}


// See if a new version is available on the internet.
- (IBAction)checkForNewVersion:(id)sender {
	NSString *currentVersion = [[[NSBundle bundleForClass:[self class]]
		infoDictionary] objectForKey:@"CFBundleVersion"];
	
	NSDictionary *internetVersionDict = [NSDictionary dictionaryWithContentsOfURL:
		[NSURL URLWithString:@"http://www.chimoosoft.com/VersionTracking/versionlist_1.xml"]];
	
	NSString *latestVersion = [internetVersionDict valueForKey:@"SimpleSearch"];
	
	if (internetVersionDict == nil) {
		//make sure we could find the new version info on the internet...
		NSRunAlertPanel(NSLocalizedString(@"ErrorOccurred", nil),
				  NSLocalizedString(@"CheckInternetTryAgain", nil),
				  NSLocalizedString(@"OK", nil), nil, nil);
		
		return;
	}
	
	
	if ([latestVersion isEqualToString: currentVersion]) {
		//your version is up to date
		NSRunAlertPanel(NSLocalizedString(@"UpToDate", nil),
				  NSLocalizedString(@"NoNewVersions", nil),
				  NSLocalizedString(@"OK", nil), nil, nil);
		
	} else {
		//a newer version is available
		
		int button = NSRunAlertPanel(NSLocalizedString(@"NewVersionAvailable", nil),
							   [NSString stringWithFormat:@"%@ %@ %@",
								   NSLocalizedString(@"Version", nil), latestVersion,
								   NSLocalizedString(@"IsNowAvailable", nil)],
							   NSLocalizedString(@"OK", nil),
							   NSLocalizedString(@"Cancel", nil), nil);
		
		if (NSOKButton == button) {
			[[NSWorkspace sharedWorkspace] openURL:[NSURL
			URLWithString:@"http://www.chimoosoft.com/simplesearch.html"]];
		}
	}
}


// Show the about panel to the user.
- (IBAction)showAboutPanel:(id)sender {

	if (aboutPanel == nil) {
		if (![NSBundle loadNibNamed:@"AboutPanel" owner:self] ) {
			NSLog(@"Load of AboutPanel.nib failed");
			return;
		}
	}

	[aboutPanel makeKeyAndOrderFront:nil];
	[urlField setURLToShow: [NSURL URLWithString: @"http://www.chimoosoft.com/"]];
	[emailField setURLToShow: [NSURL URLWithString: @"mailto:support@chimoosoft.com"]];	
}


- (void)setupMouseMovedTimer {
	mouseMovedTimer = [[NSTimer scheduledTimerWithTimeInterval:0.1
														target:self
													  selector:@selector(mouseMovedTick)
													  userInfo:0
													   repeats:YES] retain];
}

- (void)mouseMovedTick {
	
	NSPoint p = [searchWindow mouseLocationOutsideOfEventStream];
	
	// Convert it to the closeBox's coordinate system.
	p = [closeBox convertPoint:p fromView: nil];
	
	if ([closeBox mouse: p inRect: [closeBox bounds]]) {
		if (mouseWasOut == YES) {
			[closeBox setImage: [NSImage imageNamed: @"RedCircle.tif"]];
		}
		
		mouseWasOut = NO;
	} else {
		if (mouseWasOut == NO) {
			[closeBox setImage: [NSImage imageNamed: @"GrayCircle.tif"]];
			
		}
		
		mouseWasOut = YES;
	}
	
	//NSLog(@"(%f, %f)", p.x, p.y);
}

- (void)destroyMouseMovedTimer {
	
	if ([mouseMovedTimer isValid]) {
		[mouseMovedTimer invalidate]; //stop it
	}
	
	[mouseMovedTimer release];
	mouseMovedTimer = nil;
}

- (IBAction)clearQueries:(id)sender {
	[queryField removeAllItems];
	[[NSUserDefaults standardUserDefaults] 
		setObject:[queryField objectValues] forKey:@"QueryHistory"];	
}


- (IBAction)gotoSite:(id)sender {
	
	if ([[NSString stringWithString:@""] isEqualToString: [queryField stringValue]]) {
		return;  // Don't respond to a blank query.
	}
	
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	// Figure out which radio button is selected.
	NSNumber * index = [NSNumber numberWithInt: [[radioButtons selectedCell] tag]];
	 
	
	// Record this radio button in the preferences so we remember it..
	[defaultRadioButtonChoices setObject: index
							forKey: [siteMenu titleOfSelectedItem]];
	
	[searchModel showWebSiteWithPluginName:[siteMenu titleOfSelectedItem]
					optionIndex:[index intValue]
					query:[queryField stringValue]];
	
	// If the query is not already in the list of recent queries,
	if ([queryField indexOfItemWithObjectValue: [queryField stringValue]] == NSNotFound) {

		[queryField addItemWithObjectValue: [queryField stringValue]];  // Add the new query

		int numToRemember = [defaults integerForKey:@"NumQueriesToRemember"];
		
		// Get rid of old queries if we have too many
		while (([queryField numberOfItems] > numToRemember) && 
		 ([queryField numberOfItems] > 0)) {
			[queryField removeItemAtIndex:0];  // Remove the oldest item..
		}	
	}
	
	[queryField setNumberOfVisibleItems: [queryField numberOfItems]];  // Only show how many we have.
	
}




- (void)readDefaultRadioButtonChoices {
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	defaultRadioButtonChoices = 
		[[NSMutableDictionary dictionaryWithCapacity:0] retain];
	
	// If this isn't the first launch.
	if ([defaults boolForKey:@"Valid Prefs"]) {
		
		[defaultRadioButtonChoices setDictionary: 
			[defaults dictionaryForKey:@"Default Plugin Options"]];
	} 

}

// For sorting the array of plugins.
int alphabeticalPluginSort(id item1, id item2, void *context) {
	return [item1 caseInsensitiveCompare:item2];
}


// Should only be called when the program is first launched.
- (void)initController {
	if (alreadyCalledInitController) {
		return;  
	}
	
	alreadyCalledInitController = YES;
	
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	
	firstLaunch = (![defaults boolForKey:@"Valid Prefs"]);
	
	// If this isn't the first launch
	if (! firstLaunch) {
		// Restore the position and size of the window using the stored values.
		[searchWindow setFrameUsingName: @"SearchWindowFrame"];
		
	} else {
		// If this is the first launch, then we should set the default preferences
		// NSLog(@"must be first launch...");
		if (!preferenceController) {
			preferenceController = [[PreferenceController alloc] init];
		}
		
	}
	
	searchModel = [[Model alloc] init];
	[searchModel readPlugins];

	if (! firstLaunch) {
		[self readDefaultRadioButtonChoices];	

		// Now restore the user's query history.
		NSArray * oldQueries = [defaults arrayForKey:@"QueryHistory"];
		NSEnumerator *qEnum = [oldQueries objectEnumerator];
		id anObject;
		while (anObject = [qEnum nextObject]) {
			[queryField addItemWithObjectValue: anObject];
		}
	}
	
	[queryField setNumberOfVisibleItems: [queryField numberOfItems]];  // Only show how many we have.

	// Now we need to update the popup menu to list all of the plugins.
	// Use addItemWithTitle: to do this.
	int i, numPlugins;
	numPlugins = [searchModel numPlugins];
	
	NSMutableArray * p = [NSMutableArray arrayWithCapacity:numPlugins];
	[p setArray: [searchModel pluginNames]];
	
	// Sort the array alphabetically by name.
	[p sortUsingFunction:alphabeticalPluginSort context:NULL];
	
	NSMenuItem * m;
	Plugin * plugin;
	
	for (i = 0; i < numPlugins; i++) {
		[siteMenu addItemWithTitle: [p objectAtIndex:i]];
		
		plugin = [searchModel pluginNamed: [p objectAtIndex: i]];
		
		m = [siteMenu lastItem];
		
		// If they have set a command key equivalent, then add it..
		if (! [[plugin keyEquivalent] isEqualToString: @""]) {
			[m setKeyEquivalentModifierMask: [plugin keyEquivalentMask]];	
			[m setKeyEquivalent: [plugin keyEquivalent]];
		}
	}
	
	[siteMenu removeItemWithTitle: @"loading..."];
	
	if (! firstLaunch) {
		// Restore the menu selection.
		[siteMenu selectItemWithTitle: [defaults stringForKey:@"Current Menu Choice"]];		
	} else {
		[siteMenu selectItemAtIndex: 0];
	}
	
	[self popupMenuChanged: siteMenu];
	
}



// Record the radio button the user has selected for each plugin.
- (void)writeDefaultRadioButtonChoices {			
	[[NSUserDefaults standardUserDefaults] 
			setObject:(NSDictionary*)defaultRadioButtonChoices
			forKey:@"Default Plugin Options"];
	
}



- (IBAction)popupMenuChanged:(id)sender {
	// We need to update the radio buttons...
	Plugin * p = [searchModel pluginNamed: [siteMenu titleOfSelectedItem]];
	if (nil == p) { return; }
	
	[p retain];

	// Set the tool tip if the plugin was submitted by someone else.
	if ([p submittedBy] != nil) {
		[siteMenu setToolTip: [NSString stringWithFormat: @"Plugin submitted by %@.", [p submittedBy]]];
	} else {
		[siteMenu setToolTip: @""];
	}

	
	int numOptions = [p numOptions];
	
	NSArray * buttons = [radioButtons cells];

	// Loop through the radio buttons.
	NSEnumerator *enumerator = [buttons objectEnumerator];
	id aButton;
	int i;
	
	while (aButton = [enumerator nextObject]) {
		i = [aButton tag];
		if ((i >= 0) && (i < numOptions)) {
			// Then the button should be set.
			[aButton setTitle: [p nameAtIndex:i]];
			[aButton setTransparent: NO];
			[aButton setEnabled:YES];
			[radioButtons setToolTip: [p nameAtIndex:i] forCell:aButton];
		} else {
			// Make it invisible.
			[aButton setTransparent: YES];
			[aButton setEnabled:NO];
			[radioButtons setToolTip:@"" forCell:aButton];
		}
		
		[aButton setState: NO]; // Turn it off.
	}
		
	// Now we should set the appropriate radio button, or set it to 
	// the 0th one if none is selected.
	int defButton = [[defaultRadioButtonChoices objectForKey:
		[siteMenu titleOfSelectedItem]] intValue];
	
	[[buttons objectAtIndex: defButton] setState: YES];

	
	[[searchWindow contentView] setNeedsDisplay: YES];
	
	[p release];
}



- (void)dealloc {
	[[NSNotificationCenter defaultCenter] removeObserver: self];
	
	[self destroyMouseMovedTimer];
	
	[searchModel release];	
	[preferenceController release];	
	[defaultRadioButtonChoices release];
	
	[super dealloc];
}

@end



@implementation Controller (NSApplicationNotifications)

- (void)awakeFromNib {
	// Note, this is called twice.. when we first init the program, and
	// when the user wants to see the about box.  
	
	[self initController];
	
}


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
	
	// Set up the DragViews so they know which side they are on - left or right.
	[leftView setIsOnLeft: YES];
	[rightView setIsOnLeft: NO];
	
	// We'll become observers for the notifications that the DragViews will send
	// when the user clicks in them.
	NSNotificationCenter *nc;
    nc = [NSNotificationCenter defaultCenter];
    [nc addObserver: self
           selector: @selector(dragViewMouseDown:)
               name: @"dragViewMouseDown"
             object: nil];
	
    [nc addObserver: self
           selector: @selector(dragViewMouseUp:)
               name: @"dragViewMouseUp"
             object: nil];	
	
	[nc addObserver: self
		   selector: @selector(preferencesUpdated:)
			   name: @"MOOPreferencesUpdated"
			 object: nil];
		
	
	[self setupMouseMovedTimer];
	[self preferencesUpdated:nil];
		
	if (! firstLaunch) {
		[self makeWindowTall: [[NSUserDefaults standardUserDefaults] boolForKey:@"WindowWasTall"]];			
	}
	
	if (firstLaunch) {
		NSRunAlertPanel(@"Welcome to Simple Search!",
				@"Simple Search is an internet search bar with a customizable XML based plugin architecture.  By default, only four plugins are installed.  To add additional plugins, follow the instructions in the Additional Plugins folder which should have been included with your download, or visit the Simple Search web page.  Enjoy.",
			NSLocalizedString(@"OK", nil), nil, nil);
		
	}

}


- (void)applicationWillTerminate:(NSNotification *)aNotification {
	//NSLog(@"ApplicationWillTerminate");
	
	// Record what search site they were on, which radio button was selected, etc.
	
	NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
	[defaults setObject:[siteMenu titleOfSelectedItem] forKey:@"Current Menu Choice"];

	[defaults setBool:YES forKey:@"Valid Prefs"];		
	[defaults setObject:[queryField objectValues] forKey:@"QueryHistory"];	
	[defaults setBool:[disclosureTriangle state] forKey:@"WindowWasTall"];
	
	// Record which radio button is selected for each plugin.	
	[self writeDefaultRadioButtonChoices];
	
	// Do this so the position and size of the window will be saved.
	[self makeWindowTall: YES];
	[searchWindow saveFrameUsingName: @"SearchWindowFrame"];
	
}	

@end
